﻿/*
This project and all of its code is/are Copyright (C) 2017  Nils Hunter Alving

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using DynamicDependencyContainer.Containers;
using InterfaceTestData.Interfaces.PropertiesOnly;
using InterfaceTestData.ImplementationClasses.PropertiesOnly;
using DynamicDependencyContainer.Interfaces;
using System.Collections.Generic;
using UnitTestSTK;
using DynamicDependencyContainerUnitTests.TestClasses;

namespace DynamicDependencyContainerContainerUnitTests
{
    [TestClass]
    public class DynamicUnityContainerTests
    {
        string _username = "AutomationTester";
        string _password = "DeerValleyIsForRichPeopleWhoCan'tSki";
        string _mongoDBConnectionString = "MongoDB";
        string _sqlDBConnectionString = "SQL Server";
        DateTime _now = DateTime.Now;
        string _formatString = "MM/dd/yyyy";
        static IDynamicUnityContainer _unity;

        [ClassInitialize]
        public static void Initialize(TestContext context)
        {
            _unity = new DynamicUnityContainer();

            _unity.RegisterInterfacesAsDataTypes(new List<Type>()
            {
                typeof(IUser),
                typeof(IDamage),
                typeof(IOperation),
                typeof(IPropertyDamageClaim)
            });
        }

        [TestMethod]
        public void Resolve_UnregisteredType_ThrowsException()
        {
            AssertThat.Throws<ArgumentException>(() =>
            {
                var user = _unity.Resolve<IVehicle>();
            });
        }

        [TestMethod]
        public void Resolve_NonInterfaceType_ThrowsException()
        {
            var unityContainer = new DynamicUnityContainer();
            unityContainer.RegisterType<IUser, User>();

            AssertThat.Throws<ArgumentException>(() =>
            {
                var obj = unityContainer.Resolve<User>();
            });
        }
        [TestMethod]
        public void Resolve_PreviouslyRegisteredTypes_TypesAreAvailableInMemory()
        {
            Assert.IsNotNull(_unity.Resolve<IUser>());
            Assert.IsNotNull(_unity.Resolve<IDamage>());
            Assert.IsNotNull(_unity.Resolve<IOperation>());
            Assert.IsNotNull(_unity.Resolve<IPropertyDamageClaim>());
        }
        [TestMethod]
        public void Resolve_NestedTypes_CanBeConstructed()
        {
            var damage = _unity.Resolve<IDamage>();
            var operation = _unity.Resolve<IOperation>();

            Assert.IsNotNull(damage);

            damage.PartOperations = new List<IOperation>() { operation };

            Assert.IsNotNull(damage.PartOperations);
            Assert.AreEqual(1, damage.PartOperations.Count);
        }
        [TestMethod]
        public void Resolve_InterfaceWithMethods_ThrowsException()
        {
            AssertThat.Throws<ArgumentException>(() =>
                _unity.RegisterInterfacesAsDataTypes(new List<Type>() { typeof(IProcessClaims) })
            );
        }
        [TestMethod]
        public void Resolve_InterfaceWithInheritance_ReturnsValidObject()
        {
            var damageClaim = _unity.Resolve<IPropertyDamageClaim>();
            Assert.IsNotNull(damageClaim);
            Assert.IsInstanceOfType(damageClaim, typeof(IPropertyDamageClaim));

            damageClaim.ClaimNumber = "123";
            damageClaim.DamageId = "741";
            damageClaim.PropertyDamageClaimId = "789";

            Assert.AreEqual("123", damageClaim.ClaimNumber);
            Assert.AreEqual("741", damageClaim.DamageId);
            Assert.AreEqual("789", damageClaim.PropertyDamageClaimId);
        }

        [TestMethod]
        public void Resolve_ClassWithDefaultConstructr_ReturnsValidObject()
        {
            var unityContainer = new DynamicUnityContainer();
            unityContainer.RegisterType<IClaimRep, ClaimRep>();
            var claimRep = unityContainer.Resolve<IClaimRep>();

            Assert.IsNotNull(claimRep);
        }

        [TestMethod]
        public void RegisterType_ClassWithNoDefaultConstructr_ThrowsException()//fix this - should resolve interface params
        {
            var unityContainer = new DynamicUnityContainer();

            AssertThat.Throws<ArgumentException>(() =>
            {
                unityContainer.RegisterType<IClaim, Claim>();
            });
        }

        [TestMethod]
        public void RegisterInstance_ClassWithNoDefaultConstructor_ReturnsValidObject()
        {
            var unityContainer = new DynamicUnityContainer();
            unityContainer.RegisterLazyInstance<IUser, User>();

            var user = unityContainer.Resolve<IUser>();
            Assert.IsNotNull(user);
            Assert.IsInstanceOfType(user, typeof(IUser));
        }

        [TestMethod]
        public void RegisterInstance_ClassWithNoDefaultConstructor_ReturnsSameObjectEachTimeResolved()
        {
            var unityContainer = new DynamicUnityContainer();
            unityContainer.RegisterLazyInstance<IUser, User>();

            var user = unityContainer.Resolve<IUser>();
            var user2 = unityContainer.Resolve<IUser>();
            Assert.IsNotNull(user);
            Assert.IsNotNull(user2);
            Assert.IsTrue(ReferenceEquals(user, user2));
        }

        [TestMethod]
        public void RegisterLazyInstance_ClassWithNoDefaultConstructr_ArgsOutOfOrder_ThrowsException()
        {
            var unityContainer = new DynamicUnityContainer();

            AssertThat.Throws<ArgumentException>(() =>
            {
                unityContainer.RegisterLazyInstance<IClaim, Claim>(new object[] { _formatString, _now });
            });
        }

        [TestMethod]
        public void RegisterLazyInstance_ClassWithNoDefaultConstructr_ReturnsValidObject()
        {
            var unityContainer = new DynamicUnityContainer();
            unityContainer.RegisterLazyInstance<IClaim, Claim>(new object[] { _now, _formatString });

            var claim = unityContainer.Resolve<IClaim>();
            var claim1 = unityContainer.Resolve<IClaim>();

            Assert.IsNotNull(claim);
            Assert.IsNotNull(claim1);
            Assert.IsTrue(ReferenceEquals(claim, claim1));
            Assert.AreEqual(_now.ToString(_formatString), claim.FormattedLossDate);
            Assert.AreEqual(_now, claim.LossDateTime);
        }

        [TestMethod]
        public void Resolve_RegisterWithInitializerFunction_ReturnsInitializedObject()
        {
            var unityContainer = new DynamicUnityContainer();
            unityContainer.RegisterType<IUser, User>((u) =>
            {
                u.UserName = _username;
                u.Password = _password;
            });

            var user = unityContainer.Resolve<IUser>();

            Assert.IsNotNull(user);
            Assert.AreEqual(_username, user.UserName);
            Assert.AreEqual(_password, user.Password);
        }

        [TestMethod]
        public void Resolve_RegisterTypeWithInitializerFunction_ReturnsNewObjectEachTimeResolved()
        {
            var unityContainer = new DynamicUnityContainer();
            unityContainer.RegisterType<IUser, User>((u) =>
            {
                u.UserName = _username;
                u.Password = _password;
            });

            var user = unityContainer.Resolve<IUser>();
            var user1 = unityContainer.Resolve<IUser>();

            Assert.IsNotNull(user);
            Assert.IsNotNull(user1);
            Assert.IsFalse(ReferenceEquals(user, user1));
            Assert.AreEqual(_password, user1.Password);
            Assert.AreEqual(_username, user1.UserName);
        }

        [TestMethod]
        public void RegisterInstance_ReturnsSameInstanceEachTimeResovled()
        {
            var unity = new DynamicUnityContainer();
            var user = new User() { UserName = _username, Password = _password };
            unity.RegisterInstance<IUser>(user);

            var user1 = unity.Resolve<IUser>();
            var user2 = unity.Resolve<IUser>();

            Assert.IsNotNull(user1);
            Assert.IsNotNull(user2);
            Assert.IsTrue(ReferenceEquals(user, user1));
            Assert.IsTrue(ReferenceEquals(user, user2));
            Assert.AreEqual(_password, user1.Password);
        }

        [TestMethod]
        public void RegisterLazyInstance_WithInitializerFunction_ReturnsInitializedObject()
        {
            var unity = new DynamicUnityContainer();
            unity.RegisterLazyInstance<IUser, User>((u) =>
             {
                 u.UserName = _username;
                 u.Password = _password;
             });

            var user = unity.Resolve<IUser>();

            Assert.IsNotNull(user);
            Assert.AreEqual(_username, user.UserName);
            Assert.AreEqual(_password, user.Password);
        }

        [TestMethod]
        public void RegisterLazyInstance_ConstructorArgsAndInitializerFunction_ReturnsValidObject()
        {
            var unity = new DynamicUnityContainer();
            unity.RegisterLazyInstance<IClaim, Claim>(new object[] { _now, _formatString }, (c) =>
             {
                 c.ClaimNumber = "1234";
             });

            var claim = unity.Resolve<IClaim>();

            Assert.IsNotNull(claim);
            Assert.AreEqual(_now, claim.LossDateTime);
            Assert.AreEqual(_now.ToString(_formatString), claim.FormattedLossDate);
            Assert.AreEqual("1234", claim.ClaimNumber);
        }
        [TestMethod]
        public void RegisterType_ConstructorArgs_ReturnsValidObject()
        {
            var unity = new DynamicUnityContainer();
            unity.RegisterType<IClaim, Claim>(new object[] { _now, _formatString });

            var claim = unity.Resolve<IClaim>();

            Assert.IsNotNull(claim);
            Assert.AreEqual(_now, claim.LossDateTime);
            Assert.AreEqual(_now.ToString(_formatString), claim.FormattedLossDate);
        }

        [TestMethod]
        public void RegisterType_ConstructorArgsOutOfOrder_ThrowsException()
        {
            var unity = new DynamicUnityContainer();

            AssertThat.Throws<ArgumentException>(() => unity.RegisterType<IClaim, Claim>(new object[] { _formatString, _now }));
        }

        [TestMethod]
        public void RegisterType_ClassWithNoDefaultConstructr_ReturnsDifferentObjectEachTime()
        {
            var unityContainer = new DynamicUnityContainer();
            unityContainer.RegisterType<IClaim, Claim>(new object[] { _now, _formatString }, (c) =>
            {
                c.ClaimNumber = "1234";
            });

            var claim = unityContainer.Resolve<IClaim>();
            var claim1 = unityContainer.Resolve<IClaim>();

            Assert.IsNotNull(claim);
            Assert.IsNotNull(claim1);
            Assert.IsFalse(ReferenceEquals(claim, claim1));
            Assert.AreEqual(_now.ToString(_formatString), claim.FormattedLossDate);
            Assert.AreEqual(_now, claim.LossDateTime);
            Assert.AreEqual("1234", claim.ClaimNumber);
        }

        [TestMethod]
        public void Resolve_ClassWithDependencyProperties_PropertyTypeNotRegistered_ThrowsException()
        {
            var unityContainer = new DynamicUnityContainer();
            unityContainer.RegisterType<ITestData, TestData>();

            AssertThat.Throws<ArgumentException>(() => { var testData = unityContainer.Resolve<ITestData>(); });
        }

        [TestMethod]
        public void Resolve_ClassWithDependencyProperties_PropertiesAreSetByDependencyContainer()
        {
            var unityContainer = DynamicUnityContainerWithITestDataRegistered();

            var testData = unityContainer.Resolve<ITestData>();

            Assert.IsNotNull(testData);
            Assert.IsNotNull(testData.RatesData);
            Assert.IsNotNull(testData.ReportViewer);
        }
        private IDynamicUnityContainer DynamicUnityContainerWithITestDataRegistered()
        {
            var unityContainer = new DynamicUnityContainer();
            unityContainer.RegisterType<ITestData, TestData>();
            unityContainer.RegisterInterfacesAsDataTypes(new List<Type>()
            {
                typeof(IRates),
                typeof(IReportViewer)
            });

            return unityContainer;
        }

        [TestMethod]
        public void Resolve_ClassWithMultipleConstructors_DefaultConstructorInvoked_ReturnsValidObject()
        {
            var unityContainer = DynamicUnityContainerWithITestDataRegistered();

            var testData = unityContainer.Resolve<ITestData>();

            Assert.IsNotNull(testData);
        }

        [TestMethod]
        public void Resolve_ClassWithMultipleConstructors_SingleInterfaceParameterConstructorInvoked_ParameterTypeNotRegistered_ThrowsException()
        {
            var unityContainer = new DynamicUnityContainer();
            unityContainer.RegisterType<ITestData, TestData>(new object[] { typeof(IUser) });
            unityContainer.RegisterInterfacesAsDataTypes(new List<Type>()
            {
                typeof(IRates),
                typeof(IReportViewer)
            });

            AssertThat.Throws<ArgumentException>(() => { var testData = unityContainer.Resolve<ITestData>(); });
        }

        [TestMethod]
        public void Resolve_ClassWithMultipleConstructors_SingleInterfaceParameterConstructorInvoked_ReturnsValidObject()
        {
            var unityContainer = new DynamicUnityContainer();
            unityContainer.RegisterType<ITestData, TestData>(new object[] { typeof(IUser) });
            unityContainer.RegisterInterfacesAsDataTypes(new List<Type>()
            {
                typeof(IRates),
                typeof(IReportViewer)
            });
            unityContainer.RegisterType<IUser, User>();

            var testData = unityContainer.Resolve<ITestData>();

            Assert.IsNotNull(testData);
            Assert.IsInstanceOfType(testData, typeof(ITestData));
            Assert.IsInstanceOfType(testData, typeof(TestData));
            Assert.AreEqual(1, testData.UserData.Count);
        }

        [TestMethod]
        public void Resolve_ConstructorWithTwoInterfaceParameters_ReturnsValidObject()
        {
            var unityContainer = CreateUnityWithDatabaseApplicationRegistered();

            var application = unityContainer.Resolve<IDatabaseApplication>();

            Assert.IsNotNull(application);
            Assert.IsInstanceOfType(application, typeof(IDatabaseApplication));
            Assert.IsInstanceOfType(application, typeof(DatabaseApplication));
            Assert.IsNotNull(application.DataStore);
            Assert.IsNotNull(application.Logger);
            Assert.IsNotNull(application.DataStore.DataAccessLayer);
            Assert.IsNotNull(application.DataStore.DataAccessLayer.DatabaseConnection);
            Assert.IsNotNull(application.Logger.FileStore);

            //TODO: wait for next nuget release of InterfaceTestData for this to work - not being set currently
            //Assert.AreEqual(databaseConnection, application.DataStore.DataAccessLayer.DatabaseConnection.ConnectionString);
        }

        [TestMethod]
        public void ResolveWithInitializerFunction_ConstructorWithTwoInterfaceParameters_ReturnsInitializedObject()
        {
            var unityContainer = CreateUnityWithDatabaseApplicationRegistered();

            var application = unityContainer.Resolve<IDatabaseApplication>((d) => d.DataStore.DataAccessLayer.DatabaseConnection.ConnectionString = _sqlDBConnectionString);

            Assert.IsNotNull(application);
            Assert.IsInstanceOfType(application, typeof(IDatabaseApplication));
            Assert.IsInstanceOfType(application, typeof(DatabaseApplication));
            Assert.IsNotNull(application.DataStore);
            Assert.IsNotNull(application.Logger);
            Assert.IsNotNull(application.DataStore.DataAccessLayer);
            Assert.IsNotNull(application.DataStore.DataAccessLayer.DatabaseConnection);
            Assert.IsNotNull(application.Logger.FileStore);
            Assert.AreEqual(_sqlDBConnectionString, application.DataStore.DataAccessLayer.DatabaseConnection.ConnectionString);
        }

        [TestMethod]
        public void ResolveTypeWithInitializerFunction_ConstructorWithTwoInterfaceParameters_ReturnsInitializedObject()
        {
            var unityContainer = CreateUnityWithDatabaseApplicationRegistered();

            var application = unityContainer.Resolve(typeof(IDatabaseApplication),
                (d) => ((IDatabaseApplication)d).DataStore.DataAccessLayer.DatabaseConnection.ConnectionString = _sqlDBConnectionString)
                as IDatabaseApplication;

            Assert.IsNotNull(application);
            Assert.IsInstanceOfType(application, typeof(IDatabaseApplication));
            Assert.IsInstanceOfType(application, typeof(DatabaseApplication));
            Assert.IsNotNull(application.DataStore);
            Assert.IsNotNull(application.Logger);
            Assert.IsNotNull(application.DataStore.DataAccessLayer);
            Assert.IsNotNull(application.DataStore.DataAccessLayer.DatabaseConnection);
            Assert.IsNotNull(application.Logger.FileStore);
            Assert.AreEqual(_sqlDBConnectionString, application.DataStore.DataAccessLayer.DatabaseConnection.ConnectionString);
        }
        private DynamicUnityContainer CreateUnityWithDatabaseApplicationRegistered()
        {
            /* Given that I want the unity container to call these constructors on the various types used below:
             * public DatabaseApplication(IDataStore dataStore, ILogger logger){ }
             * public DataStore(IDataAccessLayer accessLayer) { }
             * public Logger(IFileStore fileStore) { }
             * public DataAccessLayer(IDatabaseConnection databaseConnection) { }
             * public DatabaseConnection(string connectionString) { }
             */
            var unityContainer = new DynamicUnityContainer();
            unityContainer.RegisterType<IDatabaseApplication, DatabaseApplication>(new object[] { typeof(IDataStore), typeof(ILogger) });
            unityContainer.RegisterType<ILogger, Logger>(new object[] { typeof(IFileStore) });
            unityContainer.RegisterType<IDataStore, DataStore>(new object[] { typeof(IDataAccessLayer) });
            unityContainer.RegisterType<IDataAccessLayer, DataAccessLayer>(new object[] { typeof(IDatabaseConnection) });
            unityContainer.RegisterType<IDatabaseConnection, DatabaseConnection>(new object[] { _mongoDBConnectionString });
            unityContainer.RegisterType<IFileStore, FileStore>();

            return unityContainer;
        }

        [TestMethod]
        public void RegisterType_ReplacePreviouslyRegisteredType_TypeIsReplaced()
        {
            var usertype = _unity.Resolve<IUser>().GetType().FullName;

            _unity.RegisterInterfacesAsDataTypes(new List<Type>()
            {
                typeof(IUser)
            });
            var newUserType = _unity.Resolve<IUser>().GetType().FullName;
            
            Assert.AreNotEqual(usertype, newUserType);
        }
        [TestMethod]
        public void ResovleType_DependencyAttributePropertiesArePopulatedInBaseclasses()
        {
            _unity.RegisterLazyInstance<IUiView,UIView>();
            _unity.RegisterType<InterfaceTestData.Interfaces.MethodsOnly.ILogger, TestLogger>();

            var uiView = _unity.Resolve<IUiView>() as UIView;

            Assert.IsNotNull(uiView);
            Assert.IsNotNull(uiView.UIViewLogger);
            Assert.IsNotNull(uiView.PublicViewLogger);
            Assert.IsNotNull(uiView.Logger);
        }
    }
}
